var Cube = Cube || {};

(function() {
	
	Cube = {
		
		/**
		 * Current version
		 * 
		 * @property {String} 
		 */
		version: '0.0.1',
		
		/**
		 * Show debug log messages
		 * 
		 * @property {Boolean} 
		 */
		debug: false,
		
		/**
		 * Empty function
		 * 
		 * @property {Function} 
		 */
		emptyFn: function() {},
		
		/**
		 * Config
		 * 
		 * @property {Object} 
		 */
		config: {},
		
		/**
		 * Run Cube 
		 */
		run: function() {
			Cube.config = {
				paths: {
					Cube: Cube.getRoot()
				}
			};
		},
		
		getRoot: function() {
			var scripts = document.getElementsByTagName("head")[0].getElementsByTagName("script");
			var path = scripts[scripts.length-1].src;
			return path.substring(0, path.lastIndexOf('/') + 1);
		},
		
		setup: function(options) {
			if (typeof options !== "object") options = {};
			if (typeof options.module === "undefined") Cube.Logger.error("Could not setup module, no module name was provided");
			if (typeof options.cache === "undefined") options.cache = false;
			if (typeof options.onLaunch !== "function") options.onLaunch = Cube.emptyFn;
			
			Cube.config.paths[options.module] 	= Cube.getRoot();
			Cube.config.cache 					= options.cache; 
			
			options.onLaunch.call();
		}
	};
	
	Cube.run();
	
})();

(function() {
	
	Cube.Logger = {
		log: function(str) {
			console.log(str);
		},
		warn: function(str) {
			console.warn(str);
		},
		error: function(str) {
			throw new Error(str);
		}
	};
	
})();


(function() {
	
	Cube.Array = {
		singleton: true,
		
		/**
		 * Slice array
		 * 
		 * @param {Array} array The array (or arguments object).
	     * @param {Number} begin The index at which to begin. Negative values are offsets from
	     * the end of the array.
	     * @param {Number} end The index at which to end. The copied items do not include
	     * end. Negative values are offsets from the end of the array. If end is omitted,
	     * all items up to the end of the array are copied.
	     * @return {Array} The copied piece of the array.
		 */
		slice: ([1,2].slice(1, undefined).length ?
	        function (array, begin, end) {
	            return Array.prototype.slice.call(array, begin, end);
	        } :
	        function (array, begin, end) {
	            if (typeof begin === 'undefined') {
	                return Array.prototype.slice.call(array);
	            }
	            if (typeof end === 'undefined') {
	                return Array.prototype.slice.call(array, begin);
	            }
	            return Array.prototype.slice.call(array, begin, end);
	        }
	    ),
	    
	    isArray: function(object) {
	    	if (object instanceof Array)
	    	{
	    		return true;
	    	}
	    	
	    	return false;
	    },
	    
	    /**
	     * Iterate over each item of an array/object. It will automatically pick the fastest way to iterate over your array or object.
	     *  
	 	 * @param {Object} array
	 	 * @param {Object} fn
	 	 * @param {Object} scope
	     */
	    each: function(array, fn, scope) {
	    	if (Cube.Array.isArray(array))
	    	{
	    		for (i = 0; i < array.length; i++)
	    		{
	    			fn.apply(scope, [array[i], i]);
	    		}
	    	}
	    	else
	    	{
	    		for (i in array)
	    		{
	    			fn.apply(scope, [array[i], i]);
	    		}
	    	}
	    }
	};
	
	Cube.each = Cube.Array.each;
	
})();


(function() {
	
	Cube.String = {
		
		/**
		 * Trim a string
		 * 
		 * 	Cube.String.trim('    Hello World   '); // returns: 'Hello World'
		 * 	Cube.String.trim('! Hello World!', '!'); // returns: ' Hello World'
		 *  
	 	 * @param {Object} str
	 	 * @param {Object} charlist
		 */
		trim: function(str, charlist) {
			var whitespace, l = 0, i = 0;
			str += '';
	
			if (!charlist) 
			{
				// default list
				whitespace = " \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000";
			} 
			else 
			{
				// preg_quote custom list
				charlist += '';
				whitespace = charlist.replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '$1');
			}
	
			l = str.length;
			for ( i = 0; i < l; i++) 
			{
				if (whitespace.indexOf(str.charAt(i)) === -1) 
				{
					str = str.substring(i);
					break;
				}
			}
	
			l = str.length;
			for ( i = l - 1; i >= 0; i--) 
			{
				if (whitespace.indexOf(str.charAt(i)) === -1) 
				{
					str = str.substring(0, i + 1);
					break;
				}
			}
	
			return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
		}
	};
	
})();


/**
 * @class Cube.Loader
 * 
 * Cube.Loader is responsible for loading classes dynamically. The {@link Cube.Loader#require Cube.Loader.require} and {@link Cube.Loader#requires Cube.Loader.requires} methods
 * are generally not called from the Loader class. Use the shorthands {@link G#require Cube.require} and {@link G#requires Cube.requires} instead.
 * 
 * ### Usage ###
 * To require only one class:
 * 
 * 	Cube.require('Cube.window.Window');
 *
 * To require multiple classes at once:
 * 
 * 	Cube.requires(['Cube.window.Window', 'Cube.another.Class', 'Cube.another.Class2']);
 * 
 * 
 * 
 * @singleton 
 */
(function() {

	Cube.Loader = {

		/**
		 * Require multiple classes
		 * 
		 * @param {Array} classes
		 */
		requires: function(classes) {
			for (i in classes)
			{
				Cube.Loader.require(classes[i]);
			}
		},

		/**
		 * Require Class
		 * 
		 * @param {String} className
		 */
		require: function(className) {
			if (!Cube.ClassManager.isDefined(className))
			{
				if (!Cube.Loader.injectScript(className))
				{
					Cube.Logger.error("Could not dynamically load: " + className);
				}
			}
		},

		/**
		 * Get contents of file
		 *  
 	 	 * @param {String} className
 	 	 * @return {String} Contents of file
		 */
		getFile: function(path, module)
		{
			var returnValue = false;
			
			var root = Cube.Loader.getRootFromClassName(module.$className);
			
			if (!root)
			{
				Cube.Logger.error("[Cube.Loader] Could not get file " + path + ". Root path wasn't found. Make sure the module object is correct!");
			}
			
			Cube.Ajax.request({
				url: root + path,
				type: 'GET',
				async: false,
				cache: Cube.config.cache,
				success: function(response) {
					returnValue = response;
				}
			});
			
			return returnValue;
		},
		
		/**
		 * Inject script
		 * 
		 * @param {String} className
		 * @param {Boolean} contents
		 */
		injectScript: function(className) {
			var parts = className.split('.'),
				returnValue = false;

			for (i in Cube.config.paths)
			{
				if (i == parts[0])
				{
					parts[0] = Cube.config.paths[i];
					parts[0] = parts[0].substr(0, parts[0].length - 1);
				}
			}

			var file = parts.join('/') + '.js';
			
			Cube.Ajax.request({
				url: file,
				type: 'GET',
				async: false,
				cache: Cube.config.cache,
				success: function(response, xhr) {
					eval(response);
					returnValue = true;
				}
			}, window);
			
			return returnValue;
		},
		
		getRootFromClassName: function(className)
		{
			var module = className.split('.')[0];
			
			for (i in Cube.config.paths)
			{
				if (module == i)
				{
					return Cube.config.paths[i];
				}
			}
			
			return false;
		}

	};

	/**
     * Convenient alias of {@link Cube.Loader#require}. Please see the introduction documentation of
     * {@link Cube.Loader} for examples.
     * 
     * @member Cube
     * @method require
     */
	Cube.require 	= Cube.Loader.require;

	/**
	 * Alias of {@link Cube.Loader#require}. Please see the documentation of {@link Cube.Loader} for more info.
	 * 
	 * @member Cube
	 * @method requires 
	 */
	Cube.requires 	= Cube.Loader.requires;

})();

(function() {
	
	Cube.Class = {
		$className: 'Cube.Class',
		
		extendingClass: {},

		/**
		 * Create class
		 * 
		 * If there is only one parameter, which is an object, it will be used as the class context.
		 * 
		 * @param {Function} 	parent		The object to inherit from
 		 * @param {Object} 		current		The current object
		 */
		create: function(parent, object) {			
			if (arguments.length == 1 && typeof parent == "object")
			{
				object = parent;
				parent = false;
			}

			// Make a global variable of the classname
			Cube.ClassManager.defineClassGlobal(object.$className);

			// Create the class constructor
			var evalStr = object.$className + ' = function() {' +
				'if (arguments[0] == Cube.Class.extendingClass) return;' +
				'this.constructor.apply(this, arguments);' +
			'};' +
			'var constuctor = ' + object.$className + ';';

			eval(evalStr);

			if (parent)
			{
				constuctor.prototype = new parent(Cube.Class.extendingClass);
			}

			// Give every method the parent method inside, so that they can call there super
			if (object)
			{
				Cube.Class.inherit(constuctor.prototype, object);
			}

			return constuctor;
		},

		/**
		 * Mixin interface
		 * 
		 * Mix an interface class into a existing Class.
		 * 
		 * @param {Object|Function} object
		 * @param {Object|Function} interface
		 * @param {Boolean} force If set to true, the interface properties will overwrite the other ones
		 * @return {Object} The original object with the interface mixed in
		 */
		mixin: function(object, interface, force) {
			var _object    = (typeof object == "function") ? object.prototype : object;
			var _interface = (typeof interface == "function") ? interface.prototype : interface;

			for (i in _interface)
			{
				if (typeof _object[i] !== 'undefined' && force !== true) continue;

				_object[i] = _interface[i];
			}

			return object;
		},

		/**
		 * Create a singleton from an constructor function
		 * 
		 * @param {Function} object
		 * @return {Object} 
		 */
		singleton: function(object) {
			var klass = new object();

			return klass;
		},

		/**
		 * Inherit members from parent object
		 * 
		 * This also creates the parent method, which can be used to call the super of the class. 
		 * 
 		 * @param {Object} dest
 		 * @param {Object} src
 		 * @param {Object} fname
		 */
		inherit : function(dest, src, fname) {
			if (arguments.length == 3) 
			{
				var ancestor 	= dest[fname];
				var descendent 	= src[fname];
				var method 		= descendent;

				descendent = function() {					
					var ref = this.parent;
					this.parent = ancestor;

					// Support for calling other methods from parent
					/*if (arguments.length > 0)
					{
						method = dest[arguments[0]];
						arguments = arguments[1] || [];
					}*/

					var result = method.apply(this, arguments);					
					ref ? this.parent = ref : delete this.parent;
					return result;
				};

				descendent.valueOf = function() {
					return method;
				};

				descendent.toString = function() {
					return method.toString();
				};

				dest[fname] = descendent;
			} 
			else 
			{
				for (var prop in src) 
				{
					if (typeof (src[prop]) == 'function') 
					{
						Cube.Class.inherit(dest, src, prop);
					} else 
					{
						dest[prop] = src[prop];
					}
				}
			}

			return dest;
		}

	};
	
})();


/**
 * @class Cube.ClassManager
 * 
 * Cube.ClassManager manages all classes in the whole framework. Generally it's not accessed directly, rather,
 * use these shorthands:
 * 
 * - {@link G#define Cube.define}
 * - {@link G#create Cube.create}
 * 
 * # Usage:
 * 
 * 	Cube.define(className, options);
 * 
 * Where options is an object representing the class itself.
 * 
 * 	@example
 * 	Cube.define('Person', {
 * 		name: '',
 * 		age: 0,
 * 
 * 		constructor: function(name, age) {
 * 			if (name) this.name = name;
 * 			if (age) this.age = age;
 * 		},
 * 
 * 		speak: function(message) {
 * 			alert("Hello, I'm " + this.name + "! This is my message for you: " + message);
 * 
 * 			return this;
 * 		}
 * 	});
 * 
 * 	var klaas = new Person('Klaas', 18);
 * 	klaas.speak('Hello again!'); // Alerts: Hello, I'm Klaas! This is my message for you: Hello again!
 * 
 * @singleton
 */
(function() { 

	Cube.ClassManager = {
		$className: "Cube.ClassManager",
		
		baseClass: "Cube.core.Base",

		/**
		 * Stores all classes that were created with Cube.create(). We store
		 * the class namespace as key.
		 *  
		 */
		classes: {},

		/**
		 * Checks if the specific class has been defined
		 * 
		 * @param {String} className
		 */
		isDefined: function(className) {
			for (i in this.classes)
			{
				if (i == className)
				{
					return true;
				}
			}

			return false;
		},

		/**
		 * Define class to the classes property
		 *  
	 	 * @param {String} className
		 */
		define: function(className, options) {
			var object = null;

			options.$className = className;

			// Support for extending classes
			if (typeof options.extend != "undefined")
			{
				Cube.require(options.extend);

				var extend = Cube.ClassManager.get(options.extend);

				// Override the current class with the extended version
				object = Cube.Class.create(extend, options);
			}
			else
			{
				if (className == Cube.ClassManager.baseClass)
				{
					object = Cube.Class.create(options);
				}
				else
				{
					Cube.require(Cube.ClassManager.baseClass);

					var baseClass = Cube.ClassManager.get(Cube.ClassManager.baseClass);
					object = Cube.Class.create(baseClass, options);
				}
			}

			// Support for implementing classes
			if (typeof options.implements != "undefined")
			{
				Cube.require(options.implements);
				
				if (options.implements instanceof Array)
				{
					for (i = 0; i < options.implements.length; i++)
					{
						var mixin = Cube.ClassManager.get(options.implements[i]);
						object = Cube.Class.mixin(object, mixin);
					}
				}
				else
				{
					var mixin = Cube.ClassManager.get(options.implements);
					object = Cube.Class.mixin(object, mixin);
				}
			}

			// Support for singletons
			if (typeof options.singleton != "undefined" && options.singleton === true)
			{
				object = Cube.Class.singleton(object);

				// Change the global scope variable too
				Cube.ClassManager.defineClassGlobal(className, object);
			}

			Cube.ClassManager.classes[className] = object;
		},

		/**
		 * Define a class to the global window object
		 * 
		 * @param {String} className 
		 */
		defineClassGlobal: function(className, object) {			
			var parts = className.split('.');
			Cube._temp = object;

			var prefix = parts[0];
			var evalString = "var " + prefix + " = " + prefix + " || {};\r\n";
			delete parts[0];

			var obj = window;

			for (i in parts)
			{
				c = parts[i];

				if (i == parts.length-1 && typeof(object) != "undefined")
				{
					evalString += prefix + "." + c + " = Cube._temp;\r\n";
				}
				else
				{
					evalString += prefix + "." + c + " = " + prefix + "." + c + " || {};\r\n";
				}
				prefix += "." + c;
			}
			
			var globalEval = function globalEval(src) {
			    if (window.execScript) {
			        window.execScript(src);
			        return;
			    }
			    var fn = function() {
			        window.eval.call(window,src);
			    };
			    fn();
			};

			globalEval(evalString);
		},

		/**
		 * Get class
		 * 
		 * @param {String} className 
		 */
		get: function(className) {
			return this.classes[className];
		},

		/**
		 * Create class
		 * 
		 * You can change de default members of the class by passing a object as second parameter.
		 *  
	  	 * @param {String} classname
	  	 * @param {Object} options
		 */
		create: function(className, options) {
			if (className != null && typeof className != 'string') {
	            //throw new Error("[Ext.define] Invalid class name '" + className + "' specified, must be a non-empty string");
	        }

			Cube.require(className);
			
			// Get Class members
			var object = Cube.ClassManager.get(className);
			
			if (options) {
				Cube.Class.mixin(object, options, true);
			}

			// Singleton
			if (typeof(object) == "object") return object;
			
			/*
			// We want to pass the arguments to the class constructor, we need to use eval, because otherwise, the constructors name
			// will be different in the console.
			var a = [],
				args = Cube.Array.slice(arguments, 1);
		    
		    for (var i = 0; i < args.length; i++)
		    {
				a[i] = 'args[' + i + ']';
		    }
			
			return eval('new object(' + a.join() + ')');*/
			return new object;
		}
	};

	/**
	 * Alias of {@link Cube.ClassManager#create Cube.ClassManager.create}. See documentation of {@link Cube.ClassManager} for more info.
	 * 
	 * @member Cube
	 * @method create 
	 */
	Cube.create = Cube.ClassManager.create;

	/**
	 * Alias of {@link Cube.ClassManager#define Cube.ClassManager.define}. See documentation of {@link Cube.ClassManager} for more info.
	 * 
	 * @member Cube
	 * @method define 
	 */
	Cube.define = Cube.ClassManager.define;
})();

Cube.define('Cube.core.Base', {
	constructor: function() {},
	
	/**
	 * Helper method to parse a config object. The first argument is the config object iself, while the second argument
	 * are the default options for that config object. If a property of the config object is undefined, it will use the
	 * one from the defaults object.
	 * 
	 * 	var defaults = {
	 * 		name: "Default name",
	 * 		age: 18,
	 * 		gender: "Male"
	 * 	};
	 * 
	 * 	var options = {};	
	 * 
	 * 	options = this.parseConfig(options, defaults);
	 * 
	 * Options will now contain all the properties from the defaults object.
	 *  
 	 * @param {Object} options
 	 * @param {Object} defaults
 	 * @return {Object}
	 */
	parseConfig: function(options, defaults) {		
		Cube.each(defaults, function(value, index) {
			if (typeof(options[index]) === "undefined")
			{
				options[index] = value;
			}
		});
		
		return options;
	},
	
	parseOptions: function(config, object) {
		for (i in config)
		{
			object[i] = config[i];
		}
	}
});


/**
 * Cube.util.Observable is an interface to manage events etc. on objects.
 * 
 * @class Cube.util.Observable 
 */
Cube.define('Cube.util.Observable', {
	
	events: {},
	
	/**
	 * Fires the event
	 * 
	 * @param {String} eventName 
	 */
	fireEvent: function(eventName) {
		var args = Cube.Array.slice(arguments, 1);
		
		if (typeof this.events[eventName] !== "undefined")
		{
			Cube.Array.each(this.events[eventName].listeners, function(fn, index) {
				this.events[eventName].listeners.splice(index, 1);
				fn.apply(this, args);
			}, this);
		}
	},
	
	/**
	 * Add event listener
	 *  
 	 * @param {String} eventName
 	 * @param {Function} fn
     * @param {Object} scope
	 */
	addListener: function(eventName, fn, scope) {
		this.events[eventName] = this.events[eventName] || { listeners: [] };
		this.events[eventName].listeners.push(fn);
	},
	
	/**
	 * Add multiple event listeners. This is a shorthand for {@link Cube.util.Observable#addListener}.
	 *  
 	 * @param {Object} object
 	 * @param {Object} scope
	 */
	on: function(object, scope)
	{
		for (i in object)
		{
			this.addListener(i, object[i], scope);
		}
	}
});


Cube.define('Cube.data.HttpRequest', {
	implements: 'Cube.util.Observable',
	
	/**
	 * Create new XMLHttpRequest
	 * 
	 * @param {String} url
	 * @param {String} type
	 * @param {Boolean} async
	 */
	create: function(url, type, async, cache) {
		var self = this;
		
		if (typeof type === 'undefined') type = 'GET';
		if (typeof async === 'undefined') async = true;
		
		if (!cache)
		{
			url += '?_' + new Date().getTime();
		}

		var xhr = this.getXhrInstance();		
		xhr.open(type, url, async);
		
		if (!cache)
		{
			xhr.setRequestHeader("Cache-Control", "no-cache");
			xhr.setRequestHeader("Pragma", "no-cache");
		}
		
		this.fireEvent('beforeSend', xhr);
		
		xhr.send(null);
		
		// For synchronous calls only
		if (async === false)
		{
			self.onReadyStateChange(xhr);
		}
		else if (async === true)
		{
			xhr.onreadystatechange = function() {
				self.onReadyStateChange(xhr);
			};
		}
	},
	
	/**
	 * On Ready State Change async event
	 * 
	 * @private 
	 */
	onReadyStateChange: function(xhr) {
		if (xhr.readyState === 4 && xhr.status === 200)
		{
			this.fireEvent('requestsuccess', xhr.responseText, xhr);
		}
	},
	
	/**
	 * Get XMLHttpRequest instance 
	 */
	getXhrInstance: function() {
        var options = [function() {
            return new XMLHttpRequest();
        }, function() {
            return new ActiveXObject('MSXML2.XMLHTTP.3.0');
        }, function() {
            return new ActiveXObject('MSXML2.XMLHTTP');
        }, function() {
            return new ActiveXObject('Microsoft.XMLHTTP');
        }], i = 0,
            len = options.length,
            xhr;

        for (; i < len; ++i) 
        {
            try 
            {
                xhr = options[i];
                xhr();
                break;
            } 
            catch(e) {}
        }
        return new xhr;
    }
});


Cube.define('Cube.Ajax', {
	implements: 'Cube.data.HttpRequest',
	singleton: true,
	
	request: function(options, scope) {
		if (typeof(options.url) === 'undefined') Cube.Logger.error("[Cube.Ajax.request] requires you to provide an url");
		
		var defaults = {
			type: 'GET',
			async: true,
			cache: false,
			beforeSend: Cube.emptyFn,
			success: Cube.emptyFn
		};
		
		options = this.parseConfig(options, defaults);
		
		this.on({
			requestsuccess: function(response, xhr) {
				options.success.call(scope, response, xhr);
			}
		}, this);
		
		this.create(options.url, options.type, options.async, options.cache);
	}
});

/**
 * Cube.Element wraps a HTMLElement inside it's own container, in where you can use more methods etc.
 * 
 * @class Cube.Element 
 */
Cube.define("Cube.Element", {
	dom: null,
	
	classes: [],
	
	listeners: {},
	
	constructor: function(el) {
		this.dom = el;
		
		return this;
	},
	
	addListener: function(evt, fn, scope) {
		var wrap = function(e) {
			if (!e) { e = window.event; }

			fn.call(scope, e);	
		};
		
		if (this.dom.addEventListener)
		{
			this.dom.addEventListener(evt, wrap, false);
		}
		else
		{
			this.dom.attachEvent('on' + evt, wrap);
		}
		
		this.listeners[evt] = this.listeners[evt] || [];
		this.listeners[evt].push(wrap);
	},
	
	removeListener: function(evt) {
		Cube.each(this.listeners[evt], function(fn, index) {		
			this.dom["on" + evt] = null;
			
			if (this.dom.removeEventListener)
			{
				this.dom.removeEventListener(evt, fn);
			}
			else
			{
				this.dom.detachEvent('on' + evt, fn);
			}
		}, this);
	},
	
	on: function(object, scope) {
		Cube.each(object, function(value, index) {
			this.addListener(index, value, scope);
		}, this);
	},
	
	/**
	 * Add class to element
	 * 
	 * If you want, you can add multiple classes at once, by adding them as arguments.
	 * 
	 * 	element.addClass('class1', 'class2', 'class3');
	 * 
	 * @param {String} className
	 */
	addClass: function(className) {
		Cube.Array.each(arguments, function(value, index) {
			this.classes.push(value);
		}, this);
		
		this.dom.className = this.classes.join(' ');
	},
	
	/**
	 * Remove a class from a component
	 * 
	 * @param {String} className 
	 */
	removeClass: function(className) {
		Cube.Array.each(arguments, function(value, index) {
			for (i = 0; i < this.classes.length; i++)
			{
				if (value == this.classes[i])
				{
					this.classes.splice(i, 1);
				}
			}
		}, this);
		
		this.dom.className = this.classes.join(' ');
	},
	
	setStyle: function(style) {
		this.dom.setAttribute('style', style);
	},
	
	getStyle: function() {
		return this.dom.getAttribute('style');
	},
	
	setPosition: function(position) {
		this.dom.style.position = position;
	},
	
	getPosition: function() {
		return this.dom.style.position;
	},
	
	setMinWidth: function(minWidth) {
		minWidth = (typeof minWidth === "number") ? minWidth + 'px' : minWidth;
		if (minWidth) this.dom.style.minWidth = minWidth;
	},
	
	setMinHeight: function(minHeight) {
		minHeight = (typeof minHeight === "number") ? minHeight + 'px' : minHeight;
		if (minHeight) this.dom.style.minHeight = minHeight;
	},
	
	setWidth: function(width) {
		width = (typeof width === "number") ? width + 'px' : width;
		if (width) this.dom.style.width = width;
	},
	
	setHeight: function(height) {
		height = (typeof height === "number") ? height + 'px' : height;
		if (height) this.dom.style.height = height;
	},
	
	setLeft: function(left) {
		left = (typeof left === "number") ? left + 'px' : left;
		if (left) this.dom.style.left = left;
	},
	
	getLeft: function() {
		return this.dom.offsetLeft;
	},
	
	setTop: function(top) {
		top = (typeof top === "number") ? top + 'px' : top;
		if (top) this.dom.style.top = top;
	},
	
	getTop: function() {
		return this.dom.offsetTop;
	},
	
	setCursor: function(cursor) {
		this.dom.style.cursor = cursor;
	},
	
	setHtml: function(html) {
		this.dom.innerHTML = html;
	}
});

/**
 * Cube.Component is the absolute base of a Cube component. It is able to listen for events and render itself.
 * 
 * @class Cube.Component
 * @mixin Cube.util.Observable
 */
Cube.define("Cube.Component", {
	implements: 'Cube.util.Observable',
	
	/**
	 * The component itself
	 * 
	 * @property {HTMLElement} 
	 */
	element: null,
	
	/**
	 * Element's classes
	 * 
	 * @property {Array} 
	 */
	classes: [],
	
	/**
	 * Constructor 
	 */
	constructor: function() {
		this.element = new Cube.Element(document.createElement('div'));

		this.setMinWidth(this.minWidth);
		this.setMinHeight(this.minHeight);
		this.setWidth(this.width);
		this.setHeight(this.height);
		this.setDraggable(this.draggable);
		
		this.setupElementEvents();
	},
	
	/**
	 * Add class to component
	 * 
	 * If you want, you can add multiple classes at once, by adding them as arguments.
	 * 
	 * 	component.addClass('class1', 'class2', 'class3');
	 * 
	 * @param {String} classname
	 */
	addClass: function(className) {
		this.element.addClass.apply(this.element, arguments);
	},
	
	/**
	 * Remove a class from a component
	 * 
	 * @param {String} className 
	 */
	removeClass: function(className) {
		this.element.removeClass.apply(this.element, arguments);
	},
	
	/**
	 * Shows and renders the component on the screen. 
	 */
	show: function() {
		this.render();
	},
	
	/**
	 * Render component to any element you want. By default, the component will be rendered to the document body.
	 * 
	 * @param {String/HTMLElement} (optional) Element to append this component
	 */
	render: function(element) {
		if (!element)
		{
			document.body.appendChild(this.element.dom);
		}
		else if (element instanceof Cube.Element)
		{
			element.dom.appendChild(this.element.dom);
		}
		else
		{
			element.appendChild(this.element.dom);
		}
	},
	
	/**
	 * 
	 */
	css: function(item, value) {
		var style = this.element.getStyle();
		var css = (style) ? Cube.String.trim(Cube.String.trim(style), ';') + ';' : '';

		this.element.setStyle(css + item + ': ' + value);
	},
	
	setMinWidth: function(minWidth) {
		this.element.setMinWidth(minWidth);
	},
	
	setMinHeight: function(minHeight) {
		this.element.setMinHeight(minHeight);
	},
	
	setWidth: function(width) {
		this.element.setWidth(width);
	},
	
	setHeight: function(height) {
		this.element.setHeight(height);
	},
	
	setHtml: function(html) {
		this.element.setHtml(html);
	},
	
	setDraggable: function(draggable) {		
		if (typeof(draggable) === "object")
		{
			var defaults = {
				delegate: this
			};
			
			var draggable = this.parseConfig(draggable, defaults);

			if (draggable.delegate instanceof Cube.Component)
			{
				draggable.delegate = draggable.delegate.element;
			}
			else if (draggable.delegate instanceof HTMLElement)
			{
				draggable.delegate = new Cube.Element(draggable.delegate);
			}
			
			// Make sure the element is absolute
			this.element.setPosition('absolute');
			
			draggable.delegate.setCursor('move');
			
			var clientX = 0, clientY = 0, offsetX = 0, offsetY = 0;
			var body = new Cube.Element(document.body);
			
			draggable.delegate.on({
				mousedown: function(e) {
					if (e.preventDefault) e.preventDefault();
					
					clientX = e.clientX, clientY = e.clientY;
					
					offsetX = this.element.getLeft();
					offsetY = this.element.getTop();
										
					body.addListener('mousemove', function(e) {
						var diffX = (e.clientX - clientX) + offsetX;
						var diffY = (e.clientY - clientY) + offsetY;
											
						this.element.setLeft(diffX);
						this.element.setTop(diffY);
					}, this);
					
					return false;
				}
			}, this);
			
			body.addListener('mouseup', function() {
				body.removeListener('mousemove');
			});
		}
	},
	
	setupElementEvents: function() {
		var el = new Cube.Element(this.element);
	}
});

